1 module hip.math.collision; 2 import hip.math.utils:sqrt; 3 import std.traits:isNumeric; 4 public import hip.math.rect; 5 public import hip.math.vector; 6 7 8 pure nothrow @nogc @safe: 9 10 bool isPointInCircle(T)(in T px, in T py, in T circleX, in T circleY, in T circleRadius) 11 if(isNumeric!T) 12 { 13 float dx = px-circleX; 14 float dy = py-circleY; 15 return sqrt(dx*dx+dy*dy) <= circleRadius; 16 } 17 18 bool isPointInCircle(in Vector2 point, in Vector2 circle, in float radius) 19 { 20 return (circle - point).mag <= radius; 21 } 22 23 24 bool isPointInRect(T)(in T px, in T py, in T rx, in T ry, in T rw, in T rh) 25 if(isNumeric!T) 26 { 27 return !(px <= rx || px >= rx+rw || py <= ry || py >= ry+rh); 28 } 29 bool isPointInRect(in Vector2 p, in Rect r) 30 { 31 return !(p.x <= r.x || p.x >= r.x+r.w || p.y <= r.y || p.y >= r.x+r.h); 32 } 33 34 35 36 ///Error AKA approximation 37 bool isPointInLine(T)(in T px, in T py, in T lx1, in T ly1, in T lx2, in T ly2, in float error = 0.01) 38 if(isNumeric!T) 39 { 40 import hip.math.utils; 41 float lineLength = (lx2 - lx1)^^2 +(ly2 - ly1)^^2; 42 float distLeft = (lx1 - px)^^2 + (ly1 - py)^^2; 43 float distRight = (lx2 - px)^^2 + (ly2 - py)^^2; 44 45 return sqrt(lineLength).approximatelyEqual(sqrt(distLeft)+sqrt(distRight), error); 46 } 47 bool isPointInLine2(T)(in T px, in T py, in T lx1, in T ly1, in T lx2, in T ly2, in float error = 0.01) 48 if(isNumeric!T) 49 { 50 import hip.math.utils; 51 52 if(px < lx1 || px > lx2) return false; 53 54 float dx = lx2 - lx1; 55 float dy = ly2 - ly1; 56 57 float slope = dy/dx; 58 float yIntercept = ly1 - (slope*lx1); 59 60 return py.approximatelyEqual(slope*px + yIntercept, error); 61 } 62 63 pragma(inline, true) 64 bool isPointInLine(T)(in T[2] point, in T[2] lineStart, in T[2] lineEnd, in float error = 0.01) 65 { 66 return isPointInLine(point[0], point[1], lineStart[0], lineStart[1], lineEnd[0], lineEnd[1], error); 67 } 68 pragma(inline, true) 69 bool isPointInLine(in Vector2 point, in Vector2 lineStart, in Vector2 lineEnd, in float error = 0.01) 70 { 71 return isPointInLine(point[0], point[1], lineStart[0], lineStart[1], lineEnd[0], lineEnd[1], error); 72 } 73 74 bool isCircleInLine(float circleX, float circleY, float radius, float lx1, float ly1, float lx2, float ly2) 75 { 76 if(isPointInCircle(lx1, ly1, circleX, circleY, radius) || isPointInCircle(lx2, ly2, circleX, circleY, radius)) 77 return true; 78 import hip.math.utils; 79 float lineDistX = lx2 - lx1; 80 float lineDistY = ly2 - ly1; 81 float lineLengthSquared = lineDistX*lineDistX + lineDistY*lineDistY; 82 83 //Dot product between circle pos distance to line start to line vector. 84 //Remember dot is the same as length squared vector, so need to divide by squared length to normalize. 85 float dot = ((circleX - lx1) * lineDistX + (circleY - ly1) * lineDistY) / lineLengthSquared; 86 87 ///Closest point from the circleX and Y to the line 88 float closestX = lx1 + dot*lineDistX, closestY = ly1 + dot*lineDistY; 89 if(!isPointInLine(closestX, closestY, lx1, ly1, lx2, ly2)) 90 return false; 91 92 //Returns if distance <= radius from the closest point in line to the circle point 93 return sqrt((closestX - circleX)^^ 2 + (closestY - circleY)^^ 2) <= radius; 94 } 95 96 bool isRayIntersectingLine(in Vector2 l1Start, in Vector2 l1End, in Vector2 l2Start, in Vector2 l2End, out Vector2 intersection) 97 { 98 Vector2 line1 = l1End - l1Start; 99 Vector2 line2 = l2End - l2Start; 100 return false; 101 } 102 103 104 bool isRayIntersectingRect(in Vector2 rayPos, in Vector2 rayEnd, in Rect rect, out Vector2 intersection, out Vector2 intersectionNormal, out float intersectionTime) 105 { 106 import hip.math.utils; 107 108 Vector2 rayDir = rayEnd - rayPos; 109 if(rayDir.magSquare == 0) 110 return false; 111 //T when hitting the near point to rayPos in X axis 112 113 Vector2 recStart = Vector2(rect.x, rect.y); 114 Vector2 recSize = Vector2(rect.w, rect.h); 115 116 Vector2 nearT = (recStart - rayPos); 117 if((nearT.x == 0 && rayDir.x == 0) || (nearT.y == 0 && rayDir.y == 0)) //Prevents NaN 118 return false; 119 nearT/= rayDir; 120 121 Vector2 farT = (recStart + recSize - rayPos); 122 if((farT.x == 0 && rayDir.x == 0) || (farT.y == 0 && rayDir.y == 0)) //Prevents NaN 123 return false; 124 farT/= rayDir; 125 126 auto swap = (ref float a, ref float b) 127 { 128 float temp = b; 129 b = a; 130 a = temp; 131 }; 132 133 if(nearT.x > farT.x) swap(nearT.x, farT.x); 134 if(nearT.y > farT.y) swap(nearT.y, farT.y); 135 //No collision 136 if(nearT.x > farT.y || nearT.y > farT.x) return false; 137 138 139 float tHitNear = max(nearT.x, nearT.y); 140 float tHitFar = min(farT.x, farT.y); 141 142 //Collision in opposite direction 143 intersectionTime = tHitNear; 144 if(tHitFar < 0 || tHitNear < 0) return false; 145 146 intersection = rayPos + tHitNear * rayDir; 147 148 //Hit on top of the rect 149 if(nearT.x > nearT.y) 150 { 151 if(rayDir.x > 0) 152 intersectionNormal = Vector2(-1, 0); 153 else 154 intersectionNormal = Vector2(1, 0); 155 } 156 //Hit on bottom of the rect 157 else 158 { 159 if(rayDir.y > 0) 160 intersectionNormal = Vector2(0, -1); 161 else 162 intersectionNormal = Vector2(0, 1); 163 } 164 return true; 165 } 166 167 /** 168 * Automatically updates Rect velocity if collision happened. If you wish a non changing velocity, send a velocity copy. 169 */ 170 bool isDynamicRectOverlappingRect(in Rect source, in Vector2 vel, in Rect target, in float deltaTime, out Vector2 intersectionNormal, out float intersectionTime) 171 { 172 if(vel.x == 0 && vel.y == 0) 173 return false; 174 175 float w = cast(float)source.w; 176 float h = cast(float)source.h; 177 Rect expandedTarget = Rect(target.x-w, target.y-h, target.w+w, target.h+h); 178 179 Vector2 intersection = void; 180 if(isRayIntersectingRect(source.position, source.position + vel*deltaTime, expandedTarget, intersection, intersectionNormal, intersectionTime) 181 && intersectionTime <= 1.0) 182 return true; 183 return false; 184 } 185 186 void resolveDynamicRectOverlappingRect(in Vector2 normal, ref Vector2 velocity, in float intersectionTime) 187 { 188 import hip.math.utils:abs; 189 velocity+= normal * Vector2(velocity.x.abs, velocity.y.abs) * (1-intersectionTime); 190 } 191 192 193 bool isRectOverlappingRect(in Rect r1, in Rect r2) 194 { 195 const float r1x2 = r1.x+r1.w; 196 const float r2x2 = r2.x+r2.w; 197 const float r1y2 = r1.y+r1.h; 198 const float r2y2 = r2.y+r2.h; 199 return !(r1x2 < r2.x || r1.x > r2x2 || r1y2 < r2.y || r1.y > r2y2); 200 } 201 202 struct RectWorld 203 { 204 private DynamicRect[] dynamicRects; 205 private Rect[] staticRects; 206 207 /** 208 * Returns a reference to the dynamic rect as they will need to be manipulated. 209 */ 210 DynamicRect* addDynamic(in Rect rect, in Vector2 velocity) 211 { 212 dynamicRects~= DynamicRect(rect, velocity); 213 return &dynamicRects[$-1]; 214 } 215 216 217 /** 218 * Should never be manipulated, which is why their reference is not returned. 219 */ 220 void addStatic(in Rect[] rect...) 221 { 222 foreach(r;rect) 223 staticRects~= r; 224 } 225 226 void update(float dt) 227 { 228 struct DynamicRectCollision 229 { 230 Vector2 normal; 231 float time; 232 } 233 foreach(ref DynamicRect dynamic; dynamicRects) 234 { 235 scope DynamicRectCollision[] collisionList; 236 foreach(const ref rect; staticRects) 237 { 238 DynamicRectCollision col = void; 239 if(isDynamicRectOverlappingRect(dynamic.rect, dynamic.velocity, rect, dt, col.normal, col.time)) 240 { 241 collisionList~= col; 242 } 243 } 244 if(collisionList.length > 0) 245 { 246 import std.algorithm.sorting:sort; 247 foreach(col; sort!((DynamicRectCollision a, DynamicRectCollision b) => a.time < b.time)(collisionList)) 248 { 249 resolveDynamicRectOverlappingRect(col.normal, dynamic.velocity, col.time); 250 } 251 } 252 destroy(collisionList); 253 dynamic.move(dynamic.velocity * dt); 254 } 255 } 256 257 258 @system int opApply(scope int delegate(ref DynamicRect) dg) 259 { 260 int result = 0; 261 foreach (ref DynamicRect item; dynamicRects) 262 { 263 result = dg(item); 264 if (result) 265 break; 266 } 267 return result; 268 } 269 270 @system int opApply(scope int delegate (const ref Rect) dg) 271 { 272 int result = 0; 273 foreach (const ref Rect item; staticRects) 274 { 275 result = dg(item); 276 if (result) 277 break; 278 } 279 return result; 280 } 281 }